package per.hnvcam.hedge.algebra.impl;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import per.hnvcam.common.OrderedSet;
import per.hnvcam.common.impl.OrderedSetImpl;
import per.hnvcam.hedge.algebra.Hedge2AlgebraDefinition;
import per.hnvcam.hedge.algebra.HedgeConstants;
import per.hnvcam.searchext.data.dao.ConfigDAO;
import per.hnvcam.searchext.data.model.Config;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

@Service("Hedge2AlgebraDefinition")
public class Hedge2AlgebraDefinitionImpl implements Hedge2AlgebraDefinition, InitializingBean {
    @Autowired
    @Qualifier("ConfigDAO")
    private ConfigDAO configDAO;

    private String positiveG;
    private String negativeG;
    private String positiveH;
    private String negativeH;
    private Map<String, Double> functionMeters;
    private Map<String, Double> cachedFunctionMeters;
    private Map<String, Integer> cachedSigns;

    public Hedge2AlgebraDefinitionImpl() {
        functionMeters = new HashMap<String, Double>();
        cachedFunctionMeters = new HashMap<String, Double>();
        cachedSigns = new HashMap<String, Integer>();
    }


    @Override
    public OrderedSet<String> getDomainOfLength(int k) {
        OrderedSet<String> result = new OrderedSetImpl<String>(getComparator());
        for (int i = 1; i <= k; i++) {
            result.addAll(getAllTermsOfLength(i));
        }
        return result;
    }

    @Override
    public OrderedSet<String> getAllTermsOfLength(int k) {
        OrderedSet<String> result = new OrderedSetImpl<String>(getComparator());
        result.add(getPositiveG());
        result.add(getNegativeG());
        for (int i = 2; i <= k; i++) {
            Stack<String> stack = new Stack<String>();
            stack.addAll(result);
            result.clear();
            while (!stack.isEmpty()) {
                String term = stack.pop();
                result.add(getPositiveH() + " " + term);
                result.add(getNegativeH() + " " + term);
            }
        }
        return result;
    }

    public String getPositiveG() {
        return positiveG;
    }

    public void setPositiveG(String positiveG) {
        this.positiveG = positiveG;
    }

    public String getNegativeG() {
        return negativeG;
    }

    public void setNegativeG(String negativeG) {
        this.negativeG = negativeG;
    }

    public String getPositiveH() {
        return positiveH;
    }

    public void setPositiveH(String positiveH) {
        this.positiveH = positiveH;
    }

    public String getNegativeH() {
        return negativeH;
    }

    public void setNegativeH(String negativeH) {
        this.negativeH = negativeH;
    }

    private void checkEmpty() {
        if (isEmpty(positiveG) || isEmpty(positiveH) || isEmpty(negativeG) || isEmpty(negativeH)) {
            throw new IllegalArgumentException("Neither generators nor hedges were set correctly.");
        }
    }

    private boolean isEmpty(String input) {
        return input == null || input.equals("");
    }

    private void checkBasicTerm(String term) {
        checkEmpty();

        if (!positiveG.equals(term) && !negativeG.equals(term) && !positiveH.equals(term) && !negativeH.equals(term)) {
            throw new IllegalArgumentException("The specified term \"" + term + "\" is invalid.");
        }
    }

    @Override
    public void setFunctionMeterValue(String term, double functionMeterValue) {
        checkEmpty();
        checkBasicTerm(term);
        functionMeters.put(term, functionMeterValue);
    }

    private double getFunctionMeterValueOfBasicTerm(String term) {
        checkBasicTerm(term);
        Double value = functionMeters.get(term);
        if (value == null) {
            throw new IllegalStateException("The value of term \"" + term + "\" was not set.");
        }
        return value.doubleValue();
    }

    public double getFunctionMeterValue(String item) {
        double value = 1.0;
        if (cachedFunctionMeters.containsKey(item)) {
            return cachedFunctionMeters.get(item);
        }
        String[] items = item.split(" ");
        for (String s : items) {
            value = value * getFunctionMeterValueOfBasicTerm(s);
        }
        cachedFunctionMeters.put(item, value);
        return value;
    }

    private int getSignOfBasicTerm(String term) {
        checkBasicTerm(term);
        if (positiveG.equals(term) || positiveH.equals(term)) {
            return 1;
        }
        return -1;
    }

    @Override
    public int getSign(String compound) {
        if (cachedSigns.containsKey(compound)) {
            return cachedSigns.get(compound);
        }
        int value;
        int idx1 = compound.indexOf(" ");
        if (idx1 < 0) {
            return getSignOfBasicTerm(compound);
        }
        String headTerm = compound.substring(0, idx1);
        String tail = compound.substring(idx1 + 1, compound.length());
        int hedgeSign = positiveH.equals(headTerm) ? 1 : negativeH.equals(headTerm) ? -1 : 0;
        if (hedgeSign == 0) {
            throw new IllegalArgumentException("An invalid combine detected: \"" + headTerm + "\" could not begin a term.");
        }
        value = hedgeSign * getSign(tail);
        cachedSigns.put(compound, value);
        return value;
    }

    @Override
    public boolean checkForConsistency() {
        if (getFunctionMeterValueOfBasicTerm(positiveG) + getFunctionMeterValueOfBasicTerm(negativeG) != 1.0) {
            throw new IllegalStateException("The total of function meter value of Generators is not equal to 1.");
        }

        if (getFunctionMeterValueOfBasicTerm(positiveH) + getFunctionMeterValueOfBasicTerm(negativeH) != 1.0) {
            throw new IllegalStateException("The total of function meter value of Hedges is not equal to 1.");
        }

        return true;
    }

    @Override
    public double getAlpha() {
        return getFunctionMeterValue(getNegativeH());
    }

    @Override
    public double getBeta() {
        return getFunctionMeterValue(getPositiveH());
    }

    @Override
    public String getNeutral() {
        return "W";
    }

    @Override
    public Comparator<String> getComparator() {
        checkEmpty();
        return new Hedge2AlgebraComparator();
    }

    @Override
    public void clearCache() {
        cachedFunctionMeters.clear();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        setNegativeG(HedgeConstants.FEW);
        setPositiveG(HedgeConstants.MANY);
        setNegativeH(HedgeConstants.LITTLE);
        setPositiveH(HedgeConstants.VERY);
        getFMValue(HedgeConstants.MANY);
        getFMValue(HedgeConstants.FEW);
        getFMValue(HedgeConstants.VERY);
        getFMValue(HedgeConstants.LITTLE);
    }

    private void getFMValue(String item) {
        Config config = configDAO.findByProperty(item);
        if (config != null && config.getValue() != null) {
            setFunctionMeterValue(item, Double.parseDouble(config.getValue()));
        } else {
            setFunctionMeterValue(item, 0.0);
        }
    }

    private class Hedge2AlgebraComparator implements Comparator<String> {

        @Override
        public int compare(String s1, String s2) {
            String[] items1 = s1.split(" ");
            String[] items2 = s2.split(" ");
            int length1 = items1.length - 1;
            int length2 = items2.length - 1;
            int result = 0;
            StringBuilder str1 = new StringBuilder();
            StringBuilder str2 = new StringBuilder();
            while (result == 0 && length1 >= 0 && length2 >= 0) {
                if (str1.length() > 0) {
                    str1.insert(0, " ");
                }
                str1.insert(0, items1[length1]);
                if (str2.length() > 0) {
                    str2.insert(0, " ");
                }
                str2.insert(0, items2[length2]);
                int sgn1 = getSign(str1.toString());
                int sgn2 = getSign(str2.toString());
                result = sgn1 == sgn2 ? 0 : sgn1 > sgn2 ? 1 : -1;
                length1--;
                length2--;
            }
            if (result == 0 && length1 >= 0) {
                if (str1.length() > 0) {
                    str1.insert(0, " ");
                }
                str1.insert(0, items1[length1]);
                result = getSign(str1.toString());
            }
            if (result == 0 && length2 >= 0) {
                if (str2.length() > 0) {
                    str2.insert(0, " ");
                }
                str2.insert(0, items2[length2]);
                result = -getSign(str2.toString());
            }
            return result;
        }
    }
}
